<?php
/**
 * @package Curly
 * @subpackage Enumeration
 * @version 0.9
 * @link http://curly.codeplex.com/
 * @license http://curly.codeplex.com/license The MIT License
 * @author Dawid Zawada
 */

namespace Curly\Enumeration
{
    /**
     * Base class for enumerations
     * 
     * Enumeration objects are created by invoking ___getInstance method 
     * from within public static methods of its children. The ___getInstance
     * method should always be invoked with the callers method name 
     * as a parameter to maintain consistency.
     * 
     * @example Example of use:
     * <code>
     * use Enumeration\Enum;
     * 
     * final class Fruit extends Enum
     * {
     *     public static function Apple()
     *     {
     *         return parent::___getInstance( __FUNCTION__ );
     *     }
     *     
     *     public static function Orange()
     *     {
     *         return parent::___getInstance( __FUNCTION__ );
     *     }
     *     
     *     //...
     * }
     * 
     * function isAnApple( Fruit $fruit )
     * {
     *     if( $fruit == Fruit::Apple() )
     *     {
     *         return "Yes, it is";
     *     }
     *     else
     *     {
     *         return "No, it isn't";
     *     }
     * }
     * 
     * echo isAnApple( Friut::Orange() );
     * </code>
     * This is rather verbose way to define enumerations, but thaks to that
     * we can make use of type hinting and syntax prompting.
     * If you don't care about the syntax prompting, you can use EnumUtils'
     * createEnum method to dynamically create enumeration classes.
     * 
     * @package Curly
 	 * @subpackage Enumeration
 	 * @license http://curly.codeplex.com/license The MIT License
     */
	abstract class Enum implements \IteratorAggregate
    {
        /**
         * Name of the enumeration member
         * 
         * @var string
         */
        protected $name;
        
        /**
         * Creates a new object of an Enum subclass
         * 
         * The object becomes in fact a value of the enumeration member,
         * whose name is passed by a parameter.
         * 
         * @internal
         * @param string $name
         */
        protected final function __construct( $name )
        {
            $this->name = $name;
        }
        
        /**
         * We don't want to have more than one object of any of the Enum class' subclasses
         */
        private final function __clone() {}
        
        /**
         * Returns the name of the enumeration member
         *
         * @return string
         */
        public function __toString()
        {
            return $this->name;
        }

        /**
         * Returns an iterator for this enumeration
         * 
         * @return \Traversable
         */
        public function getIterator()
        {
            return new \ArrayIterator( EnumUtils::getValues( get_called_class() ) );
        }
        
        
        /**
         * Collection of instances of enumeration classes
         * 
         * @var array
         */
        private static $instances = array();
        
        /**
         * Returns a new object of the class that invoked this method
         * (one of the Enum class' subclasses)
         * 
         * The object becomes an enumeration value associated with
         * the given name
         * 
         * The name cannot be empty, cannot start with more than
         * one underscore character and must obey the rules for method
         * names of the PHP language.
         * 
         * @param string $name
         * @throws \InvalidArgumentException
         */
        protected final static function ___getInstance( $name )
        {
            EnumUtils::_validateName( $name );

            $class = get_called_class();
            if( !isset( self::$instances[$class][$name] ) )
            {
                self::$instances[$class][$name] = new static( $name );
            }
            
            return self::$instances[$class][$name];
        }
    }
}